← Writeups

JWT authentication bypass via kid header path traversal

kid path traversal: el servidor lee la clave de verificación del filesystem usando el valor del header kid, permitiendo apuntar a /dev/null y firmar con el secreto vacío.

Target Overview & Vulnerability Summary

El servidor usa JWT para las sesiones y, para verificar la firma, utiliza el parámetro kid del header para recuperar la clave desde su sistema de archivos. Como el kid es controlable y no se valida, se puede forzar al servidor a usar cualquier archivo arbitrario como clave de verificación. Usando path traversal hacia /dev/null (un archivo vacío), la clave de verificación es una cadena vacía y se firma el token con ese mismo secreto.

Campo Valor
Severidad Alta (bypass de autenticación → GET /admin y eliminación de carlos)
Endpoint vulnerable POST /login, GET /admin
Root cause kid (controlable por el atacante) usado directamente como ruta de archivo para obtener la clave de verificación, sin saneamiento de path traversal

Technical Breakdown & Exploitation Flow

El laboratorio usa un algoritmo de encriptación simétrica (HS256). El JWT emitido tras el login con wiener:peter:

{
  "kid": "dd8b2c75-ee9a-4955-8d0b-a7b29fa97455",
  "alg": "HS256"
}
{
  "iss": "portswigger",
  "exp": 1757265493,
  "sub": "wiener"
}

El enunciado describe que para verificar la firma el servidor usa el parámetro kid del header para fetch de la clave desde el filesystem del servidor web. Ese valor es una ruta del sistema de archivos, por lo que un atacante puede forzar al servidor a utilizar un archivo arbitrario como clave de verificación.

Paso 1 — Elegir el archivo objetivo y derivar el secreto

/dev/null es un archivo vacío: recuperarlo devuelve una cadena vacía. Por tanto, si el token se firma con un secreto igual al contenido del archivo (la cadena vacía, un null byte), la firma será válida para el servidor.

Codificación del null byte a base64 (formato en el que el secreto se representa en las herramientas de JWT):

printf '\0' | base64

Resultado:

AA==

Paso 2 — Crear la clave simétrica con el secreto vacío

Con el plugin JWT Editor: crear una New Symmetric Key, modificar el campo k por el null byte codificado en base64 (AA==) y guardar.

Paso 3 — Path traversal en kid y firma

En Repeater, sobre la request con el JWT, modificar el header kid con un path traversal hacia /dev/null y mantener alg: HS256:

{
    "kid": "../../../../../../../../../../dev/null",
    "alg": "HS256"
}

Firmar el token con la clave simétrica creada (el secreto vacío). A partir de ahí, probar enviando requests y firmando con cada cambio. Verificar que la ruta de la solicitud GET no apunte a un usuario como wiener: usar directamente /admin o /my-account sin parámetros. Finalmente DELETE /admin?username=carlos.

Root Cause Analysis

El defecto es la interpolación directa del kid en una ruta de filesystem. El verificador interpreta kid como una ubicación de archivo y lo concatena sin validar componentes de path traversal (../). /dev/null resuelve a un archivo existente y legible cuyo contenido es vacío, de modo que la clave de verificación efectiva es el secreto vacío, conocido por el atacante:

// defectuoso: kid controlado por el atacante → ruta de archivo
keyPath := filepath.Join(keyDir, header.Kid) // "../../../../../../../../../../dev/null"
secret, err := os.ReadFile(keyPath)          // lee /dev/null → []byte{}
sig, _ := hmac.New(sha256, secret)           // firma con secreto vacío

Secure Remediation

  • Nunca usar el kid como ruta del filesystem. El verificador debe mantener un mapeo autorizado kid → clave en memoria o en un keystore, sin tocar el filesystem:
key, ok := keyStore[header.Kid] // mapa estático kid → clave
if !ok {
    return errors.New("unknown kid")
}
sig, _ := hmac.New(sha256, key)
  • Validar el formato del kid (p. ej. UUID o identificador alfanumérico con regex) y rechazar cualquier valor con ../, barras o separadores de ruta.
  • Si es imprescindible leer de disco, resolver filepath.Clean y comprobar que el resultado quede dentro del directorio de claves (deny path traversal).
  • Limitar la longitud y el juego de caracteres permitidos del kid.